{
  "name": "Case 70 - Healthcare Clinic Patient Experience Monitor",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1008,
        -160
      ],
      "id": "533e9fc1-9408-4379-9521-8a9b1526b168",
      "name": "Weekly Schedule - Monday 9 AM"
    },
    {
      "parameters": {
        "actorId": {
          "__rl": true,
          "value": "Xb8osYTtOjlsgI6k9",
          "mode": "id",
          "cachedResultName": "Google Maps Scraper"
        },
        "customBody": "{\n  \"language\": \"en\",\n  \"maxReviews\": 100,\n  \"personalData\": true,\n  \"startUrls\": [\n    {\n      \"url\": \"https://www.google.com/maps/place/CityMD+West+42nd+Urgent+Care+-+NYC/@40.7583265,-73.9916673,17z/data=!3m1!5s0x89c25853033f1761:0x685dc63a8f462cb0!4m16!1m9!3m8!1s0x89c25853036c8e7b:0xbf0a8c2f15a99a62!2sCityMD+West+42nd+Urgent+Care+-+NYC!8m2!3d40.7583265!4d-73.9916673!9m1!1b1!16s%2Fg%2F11bx1zbxmy!3m5!1s0x89c25853036c8e7b:0xbf0a8c2f15a99a62!8m2!3d40.7583265!4d-73.9916673!16s%2Fg%2F11bx1zbxmy?entry=ttu&g_ep=EgoyMDI1MTIwOS4wIKXMDSoASAFQAw%3D%3D\"\n    }\n  ],\n  \"reviewsSort\": \"newest\",\n  \"reviewsOrigin\": \"all\"\n}"
      },
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        -832,
        -160
      ],
      "id": "75bbf1bc-35b1-404a-917d-7786563e2fa9",
      "name": "Scrape Clinic Reviews",
      "credentials": {
        "apifyApi": {
          "id": "w5S6YBbbyUddEfQA",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "resource": "Datasets",
        "datasetId": "={{ $json.defaultDatasetId }}",
        "limit": 100
      },
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        -640,
        -160
      ],
      "id": "140d4177-2326-4f38-a163-ad72cf6053ae",
      "name": "Get Dataset Items",
      "credentials": {
        "apifyApi": {
          "id": "w5S6YBbbyUddEfQA",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "filter-1star",
              "leftValue": "={{ $json.stars }}",
              "rightValue": 1,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            },
            {
              "id": "filter-2star",
              "leftValue": "={{ $json.stars }}",
              "rightValue": 2,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [
        -432,
        -160
      ],
      "id": "80556838-f0e6-40fc-ac08-35f89f8515f8",
      "name": "Filter 1-2 Star Reviews"
    },
    {
      "parameters": {
        "jsCode": "// Filter out reviews without meaningful text\nconst items = $input.all();\nconst validItems = [];\n\nitems.forEach((item, index) => {\n  const text = item.json.text || item.json.textTranslated || '';\n  \n  // Skip if text is empty or too short\n  if (text.trim().length < 10) {\n    console.log(`Skipping item ${index}: Empty or too short review text`);\n    return;\n  }\n  \n  validItems.push(item);\n});\n\nconsole.log(`Filtered: ${items.length} total → ${validItems.length} valid reviews with text`);\n\nreturn validItems;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -240,
        -160
      ],
      "id": "96029e3c-6620-4bec-aa54-234ed1294e16",
      "name": "Validate Review Text"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "GPT-4o"
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a Healthcare Operations Analyst. Analyze the patient review and return ONLY this exact JSON format with NO other text:\n\n{\n  \"complaint_category\": \"Wait Time|Staff Courtesy|Cleanliness|Treatment Quality|Billing Issues|Facility Comfort|Other\",\n  \"severity\": \"critical|high|medium|low\",\n  \"emotional_tone\": \"Angry|Frustrated|Disappointed|Calm|Sarcastic\",\n  \"specific_issue\": \"what went wrong\",\n  \"key_quote\": \"most important sentence from review\"\n}\n\nCATEGORIES:\n\nWait Time = Long waits, appointment delays\nKeywords: waited hours, long wait, appointment delayed, running late\nExample: \"Waited 2 hours past appointment time\" → Wait Time\n\nStaff Courtesy = Rude staff, poor bedside manner, dismissive attitude\nKeywords: rude, dismissive, unprofessional, condescending, wouldn't listen\nExample: \"Nurse was very rude\" → Staff Courtesy\n\nCleanliness = Dirty facilities, unsanitary conditions\nKeywords: dirty, filthy, unsanitary, not clean, gross bathroom\nExample: \"Waiting room was dirty\" → Cleanliness\n\nTreatment Quality = Misdiagnosis, poor care, treatment didn't work\nKeywords: misdiagnosed, wrong treatment, didn't help, made worse, incompetent\nExample: \"Doctor misdiagnosed my condition\" → Treatment Quality\n\nBilling Issues = Surprise charges, insurance problems, overcharged\nKeywords: overcharged, surprise bill, insurance denied, billing error\nExample: \"Billed $500 more than quoted\" → Billing Issues\n\nFacility Comfort = Uncomfortable waiting area, parking issues, temperature\nKeywords: uncomfortable chairs, no parking, too cold, too hot\nExample: \"Waiting room chairs very uncomfortable\" → Facility Comfort\n\nOther = Everything else\n\nSEVERITY:\n- critical: Medical errors, safety issues, potential harm to patient\n- high: Very serious complaints, treatment failures, major issues\n- medium: Frustrating experience, poor service\n- low: Minor inconveniences\n\nEMOTIONAL TONE:\n- Angry: Profanity, threats, extreme anger\n- Frustrated: Multiple complaints, exasperated\n- Disappointed: Unmet expectations\n- Calm: Neutral, constructive feedback\n- Sarcastic: Mockery, irony\n\nReturn ONLY the JSON. No explanation. Start with { and end with }"
            },
            {
              "content": "=Analyze this patient review:\n\nClinic: {{ $json.title }}\nPatient: {{ $json.name }}\nRating: {{ $json.stars }} stars\nDate: {{ $json.publishedAtDate }}\nReview: {{ $json.text }}\n\nReturn only JSON."
            }
          ]
        },
        "builtInTools": {},
        "options": {
          "maxTokens": 500,
          "temperature": 0
        }
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [
        -80,
        -160
      ],
      "id": "098f2810-aabe-4852-b6cf-b58371eca811",
      "name": "AI: Analyze Patient Feedback",
      "credentials": {
        "openAiApi": {
          "id": "ICwxUBbatsF2sDvy",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// ENHANCED PARSER WITH VALIDATION\nconst items = [];\nconst input = $input.all();\n\nfunction extractJSON(text) {\n  const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n  if (!jsonMatch) return null;\n  return jsonMatch[0];\n}\n\ninput.forEach((item, index) => {\n  try {\n    let aiText = item.json.output[0].content[0].text || '';\n    \n    // Clean markdown and extra text\n    aiText = aiText\n      .replace(/```json/gi, '')\n      .replace(/```/g, '')\n      .trim();\n    \n    // Extract JSON\n    const jsonStr = extractJSON(aiText);\n    \n    if (!jsonStr) {\n      throw new Error('No JSON found in response');\n    }\n    \n    // Parse\n    const parsed = JSON.parse(jsonStr);\n    \n    // Validate required fields\n    if (!parsed.complaint_category || !parsed.severity || !parsed.emotional_tone) {\n      throw new Error('Missing required fields');\n    }\n    \n    items.push({ json: parsed });\n    \n  } catch (error) {\n    console.error(`Parse error for item ${index}:`, error.message);\n    \n    items.push({\n      json: {\n        complaint_category: 'Other',\n        severity: 'low',\n        emotional_tone: 'Calm',\n        specific_issue: 'Parse error',\n        key_quote: ''\n      }\n    });\n  }\n});\n\nreturn items;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        -160
      ],
      "id": "d9cd54cd-35fb-4e39-8e9e-bbc4e9c49cc2",
      "name": "Parse AI Response"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cat",
              "name": "complaint_category",
              "value": "={{ $json.complaint_category }}",
              "type": "string"
            },
            {
              "id": "sev",
              "name": "severity",
              "value": "={{ $json.severity }}",
              "type": "string"
            },
            {
              "id": "tone",
              "name": "emotional_tone",
              "value": "={{ $json.emotional_tone }}",
              "type": "string"
            },
            {
              "id": "issue",
              "name": "specific_issue",
              "value": "={{ $json.specific_issue && $json.specific_issue.includes('Parse error') ? 'Not sufficient data available' : $json.specific_issue }}",
              "type": "string"
            },
            {
              "id": "quote",
              "name": "key_quote",
              "value": "={{ $json.key_quote && $json.key_quote !== '' ? $json.key_quote : 'No quote available' }}",
              "type": "string"
            },
            {
              "id": "date",
              "name": "review_date",
              "value": "={{ $('Filter 1-2 Star Reviews').item.json.publishedAtDate }}",
              "type": "string"
            },
            {
              "id": "patient",
              "name": "patient_name",
              "value": "={{ $('Filter 1-2 Star Reviews').item.json.name }}",
              "type": "string"
            },
            {
              "id": "rating",
              "name": "star_rating",
              "value": "={{ $('Filter 1-2 Star Reviews').item.json.stars }}",
              "type": "number"
            },
            {
              "id": "text",
              "name": "review_text",
              "value": "={{ $('Filter 1-2 Star Reviews').item.json.textTranslated || $('Filter 1-2 Star Reviews').item.json.text }}",
              "type": "string"
            },
            {
              "id": "url",
              "name": "review_url",
              "value": "={{ $('Filter 1-2 Star Reviews').item.json.reviewUrl }}",
              "type": "string"
            },
            {
              "id": "clinic",
              "name": "clinic_name",
              "value": "={{ $('Filter 1-2 Star Reviews').item.json.address }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        448,
        -160
      ],
      "id": "fcbac3db-cfb7-4e1d-bcbe-b1f0baafb999",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "1kYiZPpCpOROeYFddtUiRQJM48i-h8QR6uteCYWbrhws",
          "mode": "list",
          "cachedResultName": "Case 70 - HEALTHCARE CLINIC - Patient Experience Monitor Log",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1kYiZPpCpOROeYFddtUiRQJM48i-h8QR6uteCYWbrhws/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1kYiZPpCpOROeYFddtUiRQJM48i-h8QR6uteCYWbrhws/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Review_Date": "={{ $json.review_date }}",
            "Patient_Name": "={{ $json.patient_name }}",
            "Star_Rating": "={{ $json.star_rating }}",
            "Review_Text": "={{ $json.review_text }}",
            "Complaint_Category": "={{ $json.complaint_category }}",
            "Severity": "={{ $json.severity }}",
            "Emotional_Tone": "={{ $json.emotional_tone }}",
            "Specific_Issue": "={{ $json.specific_issue }}",
            "Key_Quote": "={{ $json.key_quote }}",
            "Review_URL": "={{ $json.review_url }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Review_Date",
              "displayName": "Review_Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Patient_Name",
              "displayName": "Patient_Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Star_Rating",
              "displayName": "Star_Rating",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Review_Text",
              "displayName": "Review_Text",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Complaint_Category",
              "displayName": "Complaint_Category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Severity",
              "displayName": "Severity",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Emotional_Tone",
              "displayName": "Emotional_Tone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Specific_Issue",
              "displayName": "Specific_Issue",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Key_Quote",
              "displayName": "Key_Quote",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Review_URL",
              "displayName": "Review_URL",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        640,
        -160
      ],
      "id": "e42c607d-f62a-4e54-9c26-9b7089f58c3d",
      "name": "Log to Google Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "LOs2dbk9lby0NfDM",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $('Edit Fields').all();\n\nif (items.length === 0) {\n  return [{ json: { total_negative_reviews: 0, category_breakdown: {}, severity_breakdown: {}, emotional_tone_breakdown: {}, top_issues: [], clinic_name: 'Unknown' } }];\n}\n\nconst categoryCount = {};\nconst severityCount = {};\nconst emotionalToneCount = {};\nconst allIssues = [];\nconst clinic_name = items[0].json.clinic_name || 'Unknown';\n\nitems.forEach(item => {\n  const data = item.json;\n  const category = data.complaint_category || 'Other';\n  categoryCount[category] = (categoryCount[category] || 0) + 1;\n  const severity = data.severity || 'low';\n  severityCount[severity] = (severityCount[severity] || 0) + 1;\n  const tone = data.emotional_tone || 'Calm';\n  emotionalToneCount[tone] = (emotionalToneCount[tone] || 0) + 1;\n  \n  allIssues.push({\n    issue: data.specific_issue || '',\n    quote: data.key_quote || '',\n    patient: data.patient_name || 'Anonymous',\n    date: data.review_date || '',\n    rating: data.star_rating || 0,\n    url: data.review_url || '',\n    severity: data.severity || 'low',\n    category: data.complaint_category || 'Other'\n  });\n});\n\nconst severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };\nallIssues.sort((a, b) => (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0));\nconst topIssues = allIssues.slice(0, 5);\n\nreturn [{ json: { total_negative_reviews: items.length, category_breakdown: categoryCount, severity_breakdown: severityCount, emotional_tone_breakdown: emotionalToneCount, top_issues: topIssues, clinic_name: clinic_name } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        832,
        -160
      ],
      "id": "d86b519b-4ccc-459d-a31c-9cddb8e7119c",
      "name": "Aggregate Summary"
    },
    {
      "parameters": {
        "sendTo": "clinicadmin@example.com",
        "subject": "=Weekly Patient Experience Report - {{ $json.clinic_name }}",
        "emailType": "text",
        "message": "=WEEKLY PATIENT EXPERIENCE REPORT\n=====================================\n{{ $json.clinic_name }}\n\nOVERVIEW:\nTotal Negative Reviews: {{ $json.total_negative_reviews }}\n\nCOMPLAINT CATEGORIES:\n{{ Object.keys($json.category_breakdown).map(cat => cat + ': ' + $json.category_breakdown[cat]).join('\\n') }}\n\nSEVERITY BREAKDOWN:\n{{ Object.keys($json.severity_breakdown).map(sev => sev.toUpperCase() + ': ' + $json.severity_breakdown[sev]).join('\\n') }}\n\nEMOTIONAL TONE:\n{{ Object.keys($json.emotional_tone_breakdown).map(tone => tone + ': ' + $json.emotional_tone_breakdown[tone]).join('\\n') }}\n\nTOP 5 PRIORITY ISSUES:\n{{ $json.top_issues.map((issue, i) => (i+1) + '. [' + issue.severity.toUpperCase() + '] ' + issue.category + '\\n   Issue: ' + issue.issue + '\\n   Quote: \"' + issue.quote + '\"\\n   Patient: ' + issue.patient + ' | ' + issue.rating + ' stars | ' + issue.date).join('\\n\\n') }}\n\nRECOMMENDED ACTIONS:\n- Address all CRITICAL issues immediately (potential medical errors/safety)\n- Review Wait Time complaints - consider staffing adjustments\n- Investigate Staff Courtesy issues - targeted training needed\n- Audit Treatment Quality concerns with medical director\n- Review billing transparency with finance team\n\nGenerated: {{ new Date().toLocaleDateString() }}",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1024,
        -160
      ],
      "id": "9f90614b-a224-40e0-9931-4b5e3d30ffd8",
      "name": "Send Weekly Report Email",
      "webhookId": "1f149918-ed68-43ed-a579-83a1b703687b",
      "credentials": {
        "gmailOAuth2": {
          "id": "cyqCGWcggZNMcSOv",
          "name": "Gmail account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Weekly Schedule - Monday 9 AM": {
      "main": [
        [
          {
            "node": "Scrape Clinic Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Clinic Reviews": {
      "main": [
        [
          {
            "node": "Get Dataset Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Dataset Items": {
      "main": [
        [
          {
            "node": "Filter 1-2 Star Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter 1-2 Star Reviews": {
      "main": [
        [
          {
            "node": "Validate Review Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Review Text": {
      "main": [
        [
          {
            "node": "AI: Analyze Patient Feedback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Analyze Patient Feedback": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Log to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Google Sheet": {
      "main": [
        [
          {
            "node": "Aggregate Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Summary": {
      "main": [
        [
          {
            "node": "Send Weekly Report Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "4f607f44-fa06-4c93-afce-8ba7febca83f",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "3a43da28588548e21903e71cf1dc3ddd65c24bf0c62e7e4b77542ffe87ad79c6"
  },
  "id": "LCupP3rQSqASb1FM",
  "tags": []
}